package org.truenewx.tnxjee.repo.jpa.hibernate.resource.jdbc.spi;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.slf4j.Logger;
import org.truenewx.tnxjee.core.Strings;
import org.truenewx.tnxjee.core.context.SpringContextHolder;
import org.truenewx.tnxjee.core.util.LogUtil;
import org.truenewx.tnxjee.core.util.StringUtil;
import org.truenewx.tnxjee.model.query.FieldOrder;
import org.truenewx.tnxjee.model.spec.DataSourceLocation;
import org.truenewx.tnxjee.model.spec.DatabaseLocation;
import org.truenewx.tnxjee.repo.jdbc.util.ConditionExpression;
import org.truenewx.tnxjee.repo.jdbc.util.QlConstants;
import org.truenewx.tnxjee.repo.jpa.condition.JpaQueryAdditionalPolicy;

public class AdditionalStatementInspector implements StatementInspector {

    private static final long serialVersionUID = -3563037212340693563L;

    private JpaQueryAdditionalPolicy additionalPolicy;
    private ClassLoader classLoader = getClass().getClassLoader();
    private final Logger logger = LogUtil.getLogger(getClass());

    private JpaQueryAdditionalPolicy getAdditionalPolicy() {
        if (this.additionalPolicy == null) {
            this.additionalPolicy = SpringContextHolder.findBean(JpaQueryAdditionalPolicy.class);
            if (SpringContextHolder.CONTEXT != null) {
                this.classLoader = SpringContextHolder.CONTEXT.getClassLoader();
            }
        }
        return this.additionalPolicy;
    }

    @Override
    public String inspect(String sql) {
        String appendedSql = append(sql);
        if (!appendedSql.equals(sql)) {
            this.logger.debug(Strings.ENTER + "OriginalSQL: {}", sql);
            this.logger.debug(Strings.ENTER + "AppendedSQL: {}", sql);
        }
        return appendedSql;
    }

    private String append(String sql) {
        if (getAdditionalPolicy() != null) {
            sql = sql.replace(Strings.ENTER, Strings.SPACE).replaceAll(" {2,}", Strings.SPACE);
            sql = Strings.SPACE + sql.trim(); // trim()后前面加空格以便于判断from的位置
            String lowerSql = sql.toLowerCase(); // 小写化以便于定位关键字

            // 不支持包含join子句的hql附加条件
            if (lowerSql.contains(QlConstants.KEYWORD_LEFT_JOIN) || lowerSql.contains(QlConstants.KEYWORD_RIGHT_JOIN)
                    || lowerSql.contains(QlConstants.KEYWORD_INNER_JOIN)
                    || lowerSql.contains(QlConstants.KEYWORD_OUTER_JOIN)) {
                this.logger.warn("The hql '{}' contains 'join' keyword, so it cannot be appended condition.",
                        sql.substring(1));
                return sql.trim();
            }

            // 处理查询语句
            if (lowerSql.startsWith(QlConstants.KEYWORD_SELECT) || lowerSql.startsWith(QlConstants.KEYWORD_FROM)) {
                return appendForSelect(sql, lowerSql);
            }
            // 处理修改语句
            if (lowerSql.startsWith(QlConstants.KEYWORD_UPDATE)) {
                return appendForUpdate(sql, lowerSql);
            }
            // 处理删除语句
            if (lowerSql.startsWith(QlConstants.KEYWORD_DELETE)) {
                return appendForDelete(sql, lowerSql);
            }
        }
        return sql;
    }

    private String appendForSelect(String sql, String lowerSql) {
        int fromIndex = lowerSql.startsWith(QlConstants.KEYWORD_FROM) ? 0 : lowerSql.indexOf(QlConstants.KEYWORD_FROM);
        int latterIndex = fromIndex + QlConstants.KEYWORD_FROM.length(); // 后部子句的位置，后续会根据判断逐渐后移

        int segmentEndIndex = lowerSql.indexOf(QlConstants.KEYWORD_WHERE);
        if (segmentEndIndex < 0) {
            segmentEndIndex = lowerSql.indexOf(QlConstants.KEYWORD_GROUP_BY);
        }
        if (segmentEndIndex < 0) {
            segmentEndIndex = lowerSql.indexOf(QlConstants.KEYWORD_ORDER_BY);
        }
        if (segmentEndIndex < 0) {
            segmentEndIndex = sql.length();
        }
        String[] classAliasSegments = sql.substring(latterIndex, segmentEndIndex).split(Strings.COMMA);
        Map<Class<?>, String> entityClassAliasMapping = new LinkedHashMap<>();
        for (int i = 0; i < classAliasSegments.length; i++) {
            String segment = classAliasSegments[i];
            // 分段长度，加上分隔符逗号的长度，最后一段+1可能导致后部子句位置最终等于查询语句长度
            latterIndex += segment.length() + 1;
            segment = segment.trim();

            String className;
            String alias = null;
            int spaceIndex1 = segment.indexOf(Strings.SPACE);
            if (spaceIndex1 > 0) { // 有空格则可能有别名
                className = segment.substring(0, spaceIndex1);
                // 第一个空格开始以及之后的为别名声明部分
                alias = segment.substring(spaceIndex1);
                // 如果以 as 开头，则去掉 as
                String lowerAlias = alias.toLowerCase();
                if (lowerAlias.startsWith(QlConstants.KEYWORD_AS)) {
                    alias = alias.substring(QlConstants.KEYWORD_AS.length()).trim();
                }
                // 最后一个分段
                if (i == classAliasSegments.length - 1) {
                    // 此时别名如果还包含有空格，则说明空格后的为其它子句部分，截掉后剩余的即为纯别名
                    alias = alias.trim();
                    int spaceIndex2 = alias.indexOf(Strings.SPACE);
                    if (spaceIndex2 > 0) {
                        latterIndex -= alias.length() - spaceIndex2; // 后部子句位置往前移
                        alias = alias.substring(0, spaceIndex2);
                    }
                }
            } else {
                className = segment;
            }
            try {
                Class<?> clazz = this.classLoader.loadClass(className);
                if (StringUtils.isBlank(alias)) {
                    alias = Strings.EMPTY;
                }
                entityClassAliasMapping.put(clazz, alias);
            } catch (ClassNotFoundException e) {
                LogUtil.warn(getClass(), e);
                // 无法解析类名，则不支持附加条件
                return sql.trim();
            }
        }

        String beforeWhereString;
        String afterWhereString;
        if (latterIndex < sql.length()) {
            beforeWhereString = sql.substring(0, latterIndex).trim();
            afterWhereString = sql.substring(latterIndex).trim();
        } else {
            beforeWhereString = sql;
            afterWhereString = Strings.EMPTY;
        }
        StringBuilder result = new StringBuilder(beforeWhereString);
        afterWhereString = Strings.SPACE + afterWhereString; // 确保以空格开头
        boolean whereRequired = false;
        if (afterWhereString.toLowerCase().startsWith(QlConstants.KEYWORD_WHERE)) {
            afterWhereString = afterWhereString.substring(QlConstants.KEYWORD_WHERE.length());
            whereRequired = true;
        }

        String originalCondition = getOriginalCondition(afterWhereString); // 原始条件
        String additionalCondition = getAdditionalCondition(entityClassAliasMapping, originalCondition);
        boolean conditionAddible = additionalCondition.length() > 0;
        // 原本不以select count(开头，且不包含order by且相关实体只有一个，才可能需要附加排序
        boolean orderByAddible = !lowerSql.startsWith(QlConstants.KEYWORD_SELECT + "count(")
                && !afterWhereString.toLowerCase().contains(QlConstants.KEYWORD_ORDER_BY)
                && entityClassAliasMapping.size() == 1;
        // 没有生成附加条件且不需要附加排序子句，则直接返回原始查询语句
        if (!conditionAddible && !orderByAddible) {
            return sql.trim();
        }
        if (conditionAddible) {
            result.append(QlConstants.KEYWORD_WHERE).append(additionalCondition);
            if (afterWhereString.trim().length() > 0 && !afterWhereString.startsWith(QlConstants.KEYWORD_ORDER_BY)
                    && !afterWhereString.startsWith(QlConstants.KEYWORD_GROUP_BY)) {
                result.append(QlConstants.JUNCTION_AND);
            }
        } else if (whereRequired) {
            result.append(QlConstants.KEYWORD_WHERE);
        }
        if (orderByAddible) {
            String additionalOrderBy = getAdditionalOrderBy(entityClassAliasMapping);
            afterWhereString += additionalOrderBy;
        }
        if (result.charAt(result.length() - 1) != ' ') {
            result.append(Strings.SPACE);
        }
        result.append(afterWhereString.trim());
        return result.toString().trim();
    }

    private String getOriginalCondition(String afterWhereString) {
        String lowerString = afterWhereString.toLowerCase();
        int index = lowerString.indexOf(QlConstants.KEYWORD_GROUP_BY);
        if (index < 0) {
            index = lowerString.indexOf(QlConstants.KEYWORD_ORDER_BY);
        }
        if (index >= 0) {
            return afterWhereString.substring(0, index);
        }
        return afterWhereString;
    }

    private String getAdditionalCondition(Map<Class<?>, String> entityClassAliasMapping, String originalCondition) {
        StringBuilder additionalCondition = new StringBuilder();
        entityClassAliasMapping.forEach((entityClass, alias) -> {
            ConditionExpression conditionExpression = getAdditionalPolicy().getCondition(entityClass);
            if (conditionExpression != null) {
                String fieldName = conditionExpression.getFieldName();
                if (StringUtils.isNotBlank(alias)) {
                    fieldName = alias + Strings.DOT + fieldName;
                }
                // 原始条件子句中不包含指定字段才附加该条件表达式
                if (!containsConditionField(originalCondition, fieldName)) {
                    additionalCondition.append(QlConstants.JUNCTION_AND).append(fieldName)
                            .append(conditionExpression.toString(false));
                }
            }
        });
        if (additionalCondition.length() > 0) {
            additionalCondition.delete(0, QlConstants.JUNCTION_AND.length());
            additionalCondition.insert(0, Strings.LEFT_BRACKET).append(Strings.RIGHT_BRACKET);
        }
        return additionalCondition.toString();
    }

    private String getAdditionalOrderBy(Map<Class<?>, String> entityClassAliasMapping) {
        StringBuilder additionalOrderBy = new StringBuilder();
        for (Map.Entry<Class<?>, String> entry : entityClassAliasMapping.entrySet()) {
            Collection<FieldOrder> fieldOrders = getAdditionalPolicy().getDefaultOrders(entry.getKey());
            if (fieldOrders != null) {
                for (FieldOrder fieldOrder : fieldOrders) {
                    if (fieldOrder != null) {
                        String alias = entry.getValue();
                        if (StringUtils.isNotBlank(alias)) {
                            additionalOrderBy.append(alias).append(Strings.DOT);
                        }
                        additionalOrderBy.append(fieldOrder).append(Strings.COMMA).append(Strings.SPACE);
                    }
                }
            }

        }
        if (additionalOrderBy.length() > 0) {
            additionalOrderBy.deleteCharAt(additionalOrderBy.length() - 2); // 去掉末尾的", "
            additionalOrderBy.insert(0, QlConstants.KEYWORD_ORDER_BY);
        }
        return additionalOrderBy.toString();
    }

    private boolean containsConditionField(String conditionString, String fieldName) {
        String pattern = "\\s+" + fieldName.replace(Strings.DOT, "\\.") + "\\s*=";
        return StringUtil.regexMatch(Strings.SPACE + conditionString, pattern);
    }

    private String appendForUpdate(String sql, String lowerSql) {
        // TODO 暂不支持
        return sql.trim();
    }

    private String appendForDelete(String sql, String lowerSql) {
        // TODO 暂不支持
        return sql.trim();
    }

    public static void main(String[] args) throws Exception {
        String sql = "select count(*) from t_database_location";
        AdditionalStatementInspector factory = new AdditionalStatementInspector();
        factory.additionalPolicy = new JpaQueryAdditionalPolicy() {
            @Override
            public ConditionExpression getCondition(Class<?> entityClass) {
                if (entityClass == DatabaseLocation.class) {
                    return new ConditionExpression("baseId", String.valueOf(0));
                }
                if (entityClass == DataSourceLocation.class) {
                    return ConditionExpression.isNotNull("sourceId");
                }
                return null;
            }

            @Override
            public Collection<FieldOrder> getDefaultOrders(Class<?> entityClass) {
                return List.of(new FieldOrder("createTime", true));
            }
        };
        String s = factory.append(sql);
        System.out.println(s + Strings.SEMICOLON);
    }

}
